package com.demon.auto.utils;

import com.demon.auto.model.dto.SignClientDto;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.Assert;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 对象属性深度对比工具类
 *
 * @version 1.0
 * @author huangrz
 * @date 2025/03/19 14:14
 */
@Slf4j
public class ObjectDeepCompareUtil {

    private static class IdentityPair {
        private final Object first;
        private final Object second;

        IdentityPair(Object first, Object second) {
            this.first = first;
            this.second = second;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }
            IdentityPair that = (IdentityPair) o;
            return first == that.first && second == that.second;
        }

        @Override
        public int hashCode() {
            return System.identityHashCode(first) ^ System.identityHashCode(second);
        }
    }

    /**
     * 判断两个对象属性值是否完全一致（深度比较）
     * @param origin 原始对象
     * @param target 目标对象
     * @param ignoreFields 需要忽略的字段名（仅作用于顶层对象）
     * @return true-属性完全一致，false-存在不同属性
     */
    public static boolean isDeepEquals(Object origin, Object target, String... ignoreFields) {
        Set<String> ignoreSet = Arrays.stream(ignoreFields).filter(StringUtils::isNotBlank).collect(Collectors.toSet());
        return deepCompare(origin, target, new HashSet<>(), ignoreSet);
    }

    /**
     * 获取变化的属性列表（深度比较）
     * @return 返回属性名及新旧值的Map
     */
    public static Map<String, Object[]> getDeepChangedFields(Object origin, Object target, String... ignoreFields) {
        Assert.notNull(origin, "Origin object must not be null");
        Assert.notNull(target, "Target object must not be null");
        Assert.isTrue(origin.getClass().equals(target.getClass()), "Objects must be same type");

        Map<String, Object[]> result = new HashMap<>();
        Set<String> ignoreSet = Arrays.stream(ignoreFields).filter(StringUtils::isNotBlank).collect(Collectors.toSet());
        deepFindChanges(origin, target, "", result, new HashSet<>(), ignoreSet);
        return result;
    }

    private static boolean deepCompare(Object origin, Object target,
                                       Set<IdentityPair> visited, Set<String> ignoreSet) {
        // 快速校验
        if (origin == target) {
            return true;
        }
        if (origin == null || target == null) {
            return false;
        }
        if (!origin.getClass().equals(target.getClass())) {
            return false;
        }

        // 检测循环引用
        IdentityPair pair = new IdentityPair(origin, target);
        if (visited.contains(pair)) {
            return true;
        }
        visited.add(pair);

        // 处理数组类型
        if (origin.getClass().isArray()) {
            return compareArrays(origin, target, visited, ignoreSet);
        }

        // 基本类型和JDK内置类型直接比较
        if (isJavaBuiltInType(origin.getClass())) {
            return origin.equals(target);
        }

        // 递归比较对象字段
        try {
            for (Field field : origin.getClass().getDeclaredFields()) {
                if (ignoreSet.contains(field.getName())) {
                    continue;
                }

                field.setAccessible(true);
                Object o1 = field.get(origin);
                Object o2 = field.get(target);

                if (!deepCompareFields(o1, o2, visited, ignoreSet)) {
                    return false;
                }
            }
        } catch (IllegalAccessException e) {
            log.error("深度比较失败", e);
            return false;
        }
        return true;
    }

    private static boolean deepCompareFields(Object o1, Object o2,
                                             Set<IdentityPair> visited, Set<String> ignoreSet) {
        // 处理null值
        if (o1 == o2) {
            return true;
        }
        if (o1 == null || o2 == null) {
            return false;
        }

        // 保留当前visited状态防止污染其他字段比较
        Set<IdentityPair> newVisited = new HashSet<>(visited);
        return deepCompare(o1, o2, newVisited, Collections.emptySet());
    }

    private static boolean compareArrays(Object array1, Object array2,
                                         Set<IdentityPair> visited, Set<String> ignoreSet) {
        int length1 = Array.getLength(array1);
        if (length1 != Array.getLength(array2)) {
            return false;
        }

        for (int i = 0; i < length1; i++) {
            Object elem1 = Array.get(array1, i);
            Object elem2 = Array.get(array2, i);
            if (!deepCompareFields(elem1, elem2, visited, ignoreSet)) {
                return false;
            }
        }
        return true;
    }

    private static void deepFindChanges(Object origin, Object target, String path,
                                        Map<String, Object[]> changes,
                                        Set<IdentityPair> visited, Set<String> ignoreSet) {
        if (origin == target) {
            return;
        }
        if (origin == null || target == null) {
            changes.put(path, new Object[]{origin, target});
            return;
        }

        if (!origin.getClass().equals(target.getClass())) {
            changes.put(path, new Object[]{origin, target});
            return;
        }

        // 处理循环引用
        IdentityPair pair = new IdentityPair(origin, target);
        if (visited.contains(pair)) {
            return;
        }
        visited.add(pair);

        // 处理数组类型
        if (origin.getClass().isArray()) {
            findArrayChanges(origin, target, path, changes, visited, ignoreSet);
            return;
        }

        // JDK内置类型直接比较
        if (isJavaBuiltInType(origin.getClass())) {
            if (!origin.equals(target)) {
                changes.put(path, new Object[]{origin, target});
            }
            return;
        }

        // 递归比较对象字段
        try {
            for (Field field : origin.getClass().getDeclaredFields()) {
                String fieldName = field.getName();
                if (ignoreSet.contains(fieldName)) {
                    continue;
                }

                String currentPath = path.isEmpty() ? fieldName : path + "." + fieldName;
                field.setAccessible(true);

                Object o1 = field.get(origin);
                Object o2 = field.get(target);

                // 保留当前visited状态防止污染其他字段比较
                Set<IdentityPair> newVisited = new HashSet<>(visited);
                deepFindChanges(o1, o2, currentPath, changes, newVisited, Collections.emptySet());
            }
        } catch (IllegalAccessException e) {
            log.error("深度查找变化失败", e);
        }
    }

    private static void findArrayChanges(Object array1, Object array2, String path,
                                         Map<String, Object[]> changes,
                                         Set<IdentityPair> visited, Set<String> ignoreSet) {
        int length1 = Array.getLength(array1);
        int length2 = Array.getLength(array2);

        if (length1 != length2) {
            changes.put(path + ".length", new Object[]{length1, length2});
            return;
        }

        for (int i = 0; i < length1; i++) {
            String indexPath = path + "[" + i + "]";
            Object elem1 = Array.get(array1, i);
            Object elem2 = Array.get(array2, i);
            deepFindChanges(elem1, elem2, indexPath, changes, visited, ignoreSet);
        }
    }

    private static boolean isJavaBuiltInType(Class<?> clazz) {
        return clazz.isPrimitive() ||
                clazz.getName().startsWith("java.") ||
                clazz.getName().startsWith("javax.");
    }

    // 使用示例

    public static void main(String[] args) {
        SignClientDto dto1 = new SignClientDto()
                .setId(1L)
                .setClientName("DemonRedmi6Pro")
                .setAlias("红米6Pro")
                .setConfig("{\"emailAddr\": \"673661807@qq.com\", \"m3Password\": \"\", \"m3Username\": \"huangrongzhen\", \"earlyMinute\": 10, \"latestPoint\": \"08:35\", \"dayRangeList\": [1, 2, 3, 4, 5], \"ignoreDateList\": ['2024-09-14'], \"screenPassword\": \"123456\", \"estimateExecMin\": 3, \"specialDateList\": ['2024-09-15']}")
                .setOpenFlag(true)
                .setValidFlag(true)
                .setLastOnlineTime(LocalDateUtil.parserLocalDateTime("2025-03-19 10:22:37"))
                .setUpdateTime(LocalDateUtil.parserLocalDateTime("2025-03-19 10:22:37"))
                .setCreateTime(LocalDateUtil.parserLocalDateTime("2025-02-14 17:29:47"));
        SignClientDto dto2 = new SignClientDto()
                .setId(1L)
                .setClientName("DemonRedmi6Pro")
                .setAlias("红米6Pro")
                .setConfig("{\"m3Password\": \"\", \"m3Username\": \"huangrongzhen\", \"earlyMinute\": 6, \"latestPoint\": \"08:35\", \"dayRangeList\": [1, 2, 3, 4, 5], \"ignoreDateList\": ['2024-09-15'], \"screenPassword\": \"123456\", \"estimateExecMin\": 2, \"specialDateList\": []}")
                .setOpenFlag(true)
                .setValidFlag(true)
                .setLastOnlineTime(LocalDateUtil.parserLocalDateTime("2025-03-19 11:22:37"))
                .setUpdateTime(LocalDateUtil.parserLocalDateTime("2025-03-19 11:22:37"))
                .setCreateTime(LocalDateUtil.parserLocalDateTime("2025-02-13 17:29:47"));

        // 1. 深度比较
        System.out.println(ObjectDeepCompareUtil.isDeepEquals(dto1, dto2,
                "", "createTime", "updateTime", "onlineFlag", "relationTime"));

        // 2. 获取详细变化
        Map<String, Object[]> changes = ObjectDeepCompareUtil.getDeepChangedFields(dto1, dto2,
                "", "createTime", "updateTime", "onlineFlag", "relationTime");
        changes.forEach((path, values) -> System.out.println("[" + path + "] origin：" + values[0] + " , target：" + values[1]));
    }

}